Операторы и конструкции С#
В данном разделе рассматриваются операторы, применяемые при построении
различных программных конструкций.
Основным оператором присваивания является оператор =. Формальное описание
данного оператора имеет вид:
<идентификатор> = <значение>;
<идентификатор> должен быть такого типа данных, который может вместить в
себя присваиваемое значение, или который знает, как обработать присваиваемое значение.
<значение> может быть числовой константой, переменной или результатом
вычисления выражения.
Если требуется изменить значение некоторой переменной с учётом её предыдущего
значения, то могут быть использованы операторы присваивания +=,
=, *=, /=, %=, &=, |=, ^=, <<=, >>=. Данные операторы выполняют указанную перед
символом = операцию между операндами, расположенными слева и справа от него, и
записывают результат в операнд, указанный слева. Например, выражение a *= b +
с; равносильно выражению a = a*(b + с);
Приведение типов
При выполнении операторов присваивания (а также других операторов) в некоторых
случаях может выполняться приведение (преобразование) типов, например, во фрагменте
программы
int a=5; double d=a;
во второй строке выполняется неявное (автоматическое) преобразование целого значения
в вещественное. Автоматическое преобразование возможно, если:
оба типа совместимы;
диапазон представления чисел целевого типа шире, чем у исходного типа.
Если требуется выполнить явное преобразование значения переменной или
выражения к некоторому типу, то используется конструкция:
(<тип данных>)<преобразуемая величина>
Естественно, что возможность явного преобразования зависит от типа данных и
преобразуемой величины.
Следует учитывать, что при выполнении выражений также производится
преобразование типов в следующем порядке (т.е. сначала делается первая проверка, при
её невыполнении вторая и т.д.):
ЕСЛИ один операнд имеет тип decimal, TO и второй продвигается к типу decimal (но
если второй операнд имеет тип float или double, результат будет ошибочным);
ЕСЛИ один операнд имеет тип double, TO и второй продвигается к типу double;
ЕСЛИ один операнд имеет тип float, TO и второй продвигается к типу float;
ЕСЛИ один операнд имеет тип ulong, TO и второй продвигается к типу ulong (но если
второй операнд имеет тип sbyte, short, int или long, результат будет ошибочным);
ЕСЛИ один операнд имеет тип long, TO и второй продвигается к типу long;
ЕСЛИ один операнд имеет тип uint, а второй имеет тип sbyte, short или int, ТО оба
операнда продвигаются к типу long;
ЕСЛИ один операнд имеет тип uint, TO и второй продвигается к типу uint;
ИНАЧЕ оба операнда продвигаются к типу int.
Таким образом, минимальный тип, используемый в выражениях int. Поэтому во
фрагменте программы
byte a=100, b=157, c; c = a+b;
во второй строке переменные a и b будут преобразованы к типу int, при присваивании
суммы переменной c возникнет ошибка и потребуется явное преобразование к byte (c =
(byte)(a+b);). Кстати, в этом случае значение c будет 1.
Если при выполнении арифметических выражений требуется отслеживать
переполнение, то может использоваться команда checked. В этом случае, при
переполнении возникнет исключительная ситуация. Например, предыдущий пример с
контролем
переполнения
записывается
следующим
образом:
c = checked((byte)(a + b));
Проверяться на переполнение может не только отдельное выражение, но и блок
операторов. В этом случае запись контроля переполнения имеет вид:
checked
{
<проверяемые операторы>
}
Для отключения контроля переполнения в отдельных выражениях или блоках
операторов используется команда unchecked. Синтаксис команды аналогичен синтаксису
команды checked.
Операторы инкремента и декремента
При написании программ часто требуется увеличение (уменьшение) значения
переменной на 1. В простейшем случае операцию увеличения можно выполнить с
помощью конструкции
<переменная> = <переменная>+1;
Однако в C# (как, впрочем, и в языках-предшественниках) выполнение такой
операции упрощено и записывается в виде
<переменная>++; или ++<переменная>;
Первый оператор называется постфиксным инкрементом, а второй – пре-
фиксным инкрементом.
При выполнении одиночной операции никаких различий между ними нет. Однако
при использовании операторов в выражениях:
для префиксного инкремента сначала выполняется инкремент, а потом используется
переменная в выражении;
для постфиксного инкремента сначала используется переменная в выражении, а потом
выполняется инкремент.
Например, во фрагменте программы
int i=1, b, c; b = i++;
c = ++i;
во второй строке сначала произойдёт присваивание, а потом будет выполнен инкремент
(после выполнения строки b=1, i=2), а в третье строке сначала выполнится инкремент, а
потом произойдёт присваивание (после выполнения строки c=3, i=3).
Аналогично операторам инкремента работают и операторы декремента
<переменная>--; или --<переменная>;
Использование операторов инкремента и декремента может приводить к плохо
читаемому коду, например:
int i=1;
i = i++ + ++i; // i = 4;
Операторные скобки {}
Операторные скобки {} применяются в случае, когда необходимо объединить несколько
операторов в единый сложный оператор. Необходимость в таких действиях возникает, когда
какой-либо оператор может выполнить только один другой оператор, а требуется
выполнение нескольких (см., например, оператор if).
Также операторные скобки применяются для обозначения начала и окончания
различных блоков программы, например, тела функции.
Условный оператор if
Условный оператор if применяется для реализации разветвления хода выполнения программы
на два направления в зависимости от некоторого условия.
Формальное описание данного оператора имеет вид:
if (<логическое значение>) <оператор если истина>; [else <оператор если ложь>;]
В качестве <логическое значение> могут выступать логические переменные, логические
константы или выражения, дающее логическое значение.
Если <логическое значение> даёт результат «Истина», то выполняется
оператор <оператор если истина>, иначе:
если задан блок else, то выполняется <оператор если ложь>;если блок else не задан, то никаких
действий не происходит.
<оператор если истина> и <оператор если ложь> являются одиноч-
ными операторами, поэтому если требуется выполнить более одного оператора, то они
объединяются с использованием операторных скобок.
Пример 1: найти минимум из двух целочисленных переменных a, b и сохранить его в
переменную min.
int a, b, min; a = ???;
b = ???;
if (a < b) min = a;
else
min = b;
a
и сохранить его в переменную res.
b c
При этом, если b+c равно 0, то перед расчётом присвоить b значение 1, а c значение 0.
double a, b, c, res; a = ???;
b = ???;
c = ???;
if (b+c == 0)
{
b = 1; c = 0;
}
res = a/(b+c);
Логические операторы «И» и «ИЛИ»
Полные и укороченные логические операторы «И» и «ИЛИ» дают один и тот же
результат, однако укороченные операторы могут работать быстрее и позволяют
реализовывать более простые конструкции.
Полные логические операторы выполняют логическое выражение полностью.
Например, в выражении a < b & c > d сначала будет вычислен результат a < b, потом c >
d и только потом рассчитан результат всего выражения с помощью оператора &. Однако, если
выражение a < b даёт результат false, то фактически уже известен конечный результат
всего выражения. Укороченные операторы учитывают это обстоятельство и в этом случае не
рассчитывают выражение c > d.
Как правило, полные логические операторы используются в том случае, если второй
и последующие операторы сложного выражения меняют значения переменных.
Укороченные операторы могут использоваться для гарантированной блокировки
выполнения действий, которые могут привести к ошибке. Например, пусть требуется
определить, делится ли значение переменной n нацело на значение переменной m. Это
можно выполнить с помощью условия
if (n%m == 0){...}
Но в случае равенства m нулю при выполнении проверки возникнет ошибка. Её можно
предотвратить используя два оператора if
if (m != 0)
if (n%m == 0){...}
или используя один оператора if с укороченным логическим «И»
if (m != 0 && n%m == 0){...}
Использование полного логического «И» недопустимо, т.к. в этом случае
при m=0 выражение n%m == 0 все равно будет вычисляться и в нем произойдёт ошибка.
Условный оператор ? :
Если для расчёта значения некоторой переменной требуется применение условного
оператора if в виде if (a) r = b; else r = c;, то целесообразно
21
применять условный оператор ? :, так как при этом упрощается запись кода и его результат
можно применять в качестве операнда более сложных выражений.
Структура условного оператора имеет вид:
<логическое значение> ? <выражение если истина> : <выражение если ложь>;
В качестве <логическое значение> могут выступать логические переменные, логические
константы или выражения, дающее логическое значение.
<выражение если истина> и <выражение если ложь> должны давать одинаковый тип
результата.
Результат работы оператора должен быть присвоен некоторой переменной или являться
частью более сложного выражения.
Пример 1: найти минимум из двух целочисленных переменных a, b и сохранить его в
переменную min.
min = a < b ? a : b; // Это аналог if (a < b) min = a; else min = b;
Пример 2: найти максимум из двух целочисленных переменных a, b и сохранить его
удвоенное значение в переменную max.
max = 2*(a > b ? a : b);
// Это аналог if (a > b) max = 2*a; else max = 2*b;
Оператор выбора switch и оператор прерывания break
Если требуется выполнить разветвление выполнения программы более чем на два
направления, то возможно применение либо нескольких вложенных операторов if, либо
оператора выбора switch.
Формальное описание оператора switch имеет вид:
switch (<значение switch>)
{
case <значение 1>: <операторы 1> break;
[case <значение 2>: <операторы 2> break; ...]
[default:
<операторы default> break;]
}
<значение switch> должно быть переменной или выражением целого, строкового,
символьного, перечисляемого, логического типа. <значение N> должны быть значениями
такого же типа. <значение N> должны быть уникальными.
Пример:
int n; string s; n = ???;
switch (n)
{
case 1:
s = "n=1"; break;
case 2:
s = "n=2"; break;
default:
s = "n<1 или n>2"; break;
}
При выполнении оператора ищется <значение N>, равное <значение switch>.
Если такое значение найдено, то выполняется соответствующий блок case. Если значение
не найдено, то:
при наличии блока default выполняется этот блок;
при отсутствии блока default никаких действий не производится.
Каждый блок case и блок default должны заканчиваться оператором break,
которые прерывает дальнейшее выполнение оператора switch. Исключением является
ситуация, когда блок case не имеет операторов. Такое решение применяется, когда
требуется, чтобы для нескольких значений выполнялись одни и те же действия, например:
int n; string s; n = ???;
switch (n)
{
case 1: case 2:
s = "n=1 или n=2"; break;
default:
s = "n<1 или n>2"; break;
}
Вместо оператора break может располагаться оператор goto case <N>,
позволяющий перейти к блоку со значением <N>.
Пример: рассчитать процент скидки, если при покупке одного товара скидки нет,
при покупке двух товаров скидка 2%, трёх – 5%, четырёх и более – 10%.
int n; // Количество товаров
int c; // Процент скидки
n = ???;
switch (n)
{
case 1: c = 0; break;
case 2: c = 2; break;
case 3: c = 5; break;
default: c = 10; break;
}
Оператор цикла for
Предназначен для реализации итерационных алгоритмов. Формальная структура
оператора имеет вид:
for ([<инициализация>]; [<условие>]; [<итерация>]) [<оператор>];
<инициализация> представляет собой операторы, подготавливающие цикл к
работе. Они выполняются один раз до начала работы цикла. Как правило в этих
операторах задаются начальные значения «параметров цикла».
<условие> определяет условие выхода из цикла и его результатом должно быть
логическое значение. <условие> проверяется перед каждой итерацией цикла, поэтому
тело цикла может не выполниться ни разу. Выход из цикла производится,
если <условие> имеет значение false. Как правило, <условие> должно зависеть от
«параметра цикла».
<итерация> определяет действия, выполняемые после каждой итерации цикла. Как
правило, в них производится изменение «параметров цикла», причём изменение может
осуществляться произвольным образом как в сторону увеличения, так и в сторону
уменьшения.
<оператор> представляет собой одиночный оператор, выполняемый на каждой
итерации цикла. Если в цикле необходимо выполнять несколько операторов, то
используются операторные скобки.
Пример: рассчитать факториал числа n.
int
n;
n =
???;
int
f = 1;
for
(int i=2; i<=n; i++)
f
*= i;
<инициализация> и <итерация> могут выполнять более одного оператора. В этом
случае операторы разделяются запятой. Предыдущий пример может быть записан
следующим образом:
int n,i,f; n = ???;
for (i=2, f=1; i<=n; f *= i, i++);
Приведённый выше фрагмент показывает, что тело цикла может быть пустым.
Также пустыми могут быть <инициализация>, <условие>, терация>. Напри-
мер, предыдущий пример может быть записан так:
int
n;
n =
???;
int
f = 1;
int
i = 2;
for
(; i<=n; )
f
*= i++;
Несмотря на то, что в блоках <инициализация> и <итерация> имеется
возможность выполнять несколько операторов, при этом оставляя тело цикла пустым,
злоупотреблять этим не следует.
Оператор цикла while
Оператор цикла while фактически представляет собой оператор цикла for, у
которого не заданы <инициализация> и <итерация>. Формальная структура данного
оператора имеет вид:
while (<условие>) <оператор>;
Как и в цикле for, <условие> определяет условие выхода из цикла и его
результатом должно быть логическое значение. <условие> проверяется перед каждой
итерацией цикла, поэтому тело цикла может не выполниться ни разу. Выход из цикла
производится, если <условие> имеет значение false.
<оператор> представляет собой одиночный оператор, выполняемый на каждой
итерации цикла. Если в цикле необходимо выполнять несколько операторов, то
используются операторные скобки.
Пример: рассчитать факториал числа n.
int n; n = ???;
int i = 1; int f = 1;
while (++i <= n) f *= i;
Оператор цикла do...while
Формальная структура данного оператора имеет вид:
do <оператор> while (<условие>);
Как и в предыдущих операторах цикла, <условие> определяет условие выхода из
цикла и его результатом должно быть логическое значение.
Однако, <условие> проверяется после каждой итерации цикла, поэтому тело цикла
выполняется как минимум один раз. Выход из цикла производится,
если <условие> имеет значение false.
<оператор> представляет собой одиночный оператор, выполняемый на каждой
итерации цикла. Если в цикле необходимо выполнять несколько операторов, то
используются операторные скобки.
Пример: рассчитать факториал числа n.
int n;
n = ???;
int i = 1; int f = 1; do
f *= i++; while (i <= n);
Операторы прерываний break (для циклов) и continue
Оператор break, используемый в операторе switch, также может применяться в циклах для
их немедленного прерывания. При его использовании управление передаются оператору,
следующему за циклом. При этом, если имеется ряд вложенных циклов, то
оператор break прерывает только тот цикл, в теле которого он находится.
Пример: найти наименьший делитель числа n, больший 1:
int n; n = ???;
int i;
for (i=2; i<=n; i++) if (n%i == 0)
break;
Оператор continue используется для прерывания текущей итерации цикла и перехода к
следующей (применяется довольно редко, т.к. как правило имеются более удобные способы
написания требуемого кода).
При использовании в теле оператора for управление передаётся в блок , после чего цикл продолжает
работать по обычной схеме.
При использовании в теле операторов while и do...while управление передаётся в
блок <условие>, после чего циклы продолжает работать по обычной схеме.
Оператор new
Оператор new используется для создания объектов и вызова конструкторов. При
создании объектов для них выделяется память, а также вызывается конструктор по
умолчанию, который инициализирует члены объектов значением по умолчанию.
Например, две ниже приведённые строки приводят к одному результату созданию
переменной i со значением 0, т.к. конструктор по умолчанию типа int присваивает
объекту значение 0:
int i=0;
int i = new int();
Значения по умолчанию для всех числовых типов имеют значение 0, для символов
пустой символ, для строки – null, для логического типа – false.
Массивы
Массив представляет собой совокупность переменных одного типа с общим для
обращения к ним именем. В языке С# массивы могут быть как одномерными, так и
многомерными, хотя чаще всего применяются одномерные массивы. Массивы служат
самым разным целям, поскольку они предоставляют удобные средства объединения
связанных вместе переменных.
Главное преимущество массива в организации данных таким образом, чтобы ими
было проще манипулировать. Как правило, обработка массива реализуется путём
циклического обращения к его элементам.
При создании массива, всем его элементам присваивается значение 0.
Одномерные массивы
Для того чтобы воспользоваться массивом в программе, требуется двухэтапная
процедура, поскольку в С# массивы реализованы в виде объектов. Во-первых, необходимо
объявить переменную, которая может обращаться к массиву. И вовторых, нужно создать
экземпляр массива, используя оператор new. Для объявления одномерного массива
обычно применяется следующая общая форма:
<тип>[] <идентификатор> = new <тип>[<размер>];
хотя возможно разделение данной строки на две части:
<тип>[] <идентификатор>; <идентификатор> = new <тип>[<размер>];
Пример обычного объявления целочисленного массива, состоящего из 10 элементов:
int[] mas = new int[10];
Нумерация элементов массива всегда начинается с нуля, поэтому в приведённом
выше примере доступны элементы массива с индексами в диапазоне 0÷9.
Для обращения к элементу массива требуется указать идентификатор массива, после
которого в квадратных скобках указать индекс требуемого элемента, например,
присвоение последнему элементу массива mas из приведённого выше примера значения
25 будет записано как:
mas[9] = 25;
При объявлении массива возможна его инициализация. В этом случае, в команде
объявления массива не требуется указания размера массива, так как он вычисляется по
количеству введённых значений инициализации. Формально строка объявления с
инициализацией имеет вид:
<тип>[] <идентификатор> = {<значение 1> [, <значение 2> ...]};
<значение 1> [, <значение 2> ... должны быть совместимы с <тип>.
Пример:
int[] mas = {7,12,3,14,65};
Также допустимым (хотя и излишним) является использование при инициализации
оператора new, например:
int[] mas = new int[5] {7,12,3,14,65};
Однако в этом случае размер массива должен совпадать с количеством значений
инициализации.
Многомерные массивы
Многомерные массивы отличаются использованием более одной размерности во всех
операциях. При этом, размерности отделяются друг от друга запятой. Формальное описание
многомерного массива может быть задано строкой
<тип>[,[, ...]] <идентификатор> =
new <тип>[<размер 1>,<размер 2>[,<размер 3> ...]];
например, приведённый ниже фрагмент создаёт трёхмерный массив и присваивает одному
из элементов значение 999
int[,,] mas = new int[3,4,5]; mas[1,2,3] = 999;
Количество элементов в многомерном массиве, созданном таким способом,
определяется как произведение количеств элементов в каждой размерности (для
приведённого выше примера: 3*4*5 → 60).
Многомерные массивы также могут быть инициализированы при создании. При
этом, значения инициализации для каждой размерности заключаются в фигурные скобки.
Например, строка
int[,,] mas = {{{1,2},{3,4},{5,6}},{{7,8},{9,10},{11,12}}};
инициализирует трёхмерный массив, имеющий размер размерностей 2,3,2.
Ступенчатые массивы
Ступенчатый массивы представляют собой особый тип многомерных массивов, у
которого во второй и последующих размерностях может быть различное количество
элементов.
Отличие в описании и использовании ступенчатых массивов, по сравнению с
многомерными, заключается в следующем:
каждая размерность заключается в отдельную пару квадратных скобок (вместо запятых,
отделяющих размерности в многомерных массивах);
т.к. каждая размерность представляет собой отдельный одномерный массив, при создании
ступенчатого массива каждая размерность создаётся индивидуально отдельными командами.
За одну команду не может быть создано несколько размер-
ностей.
Формально, объявление и начало создания ступенчатого массива может быть
записано строкой
<тип>[][][[] ...] <идентификатор> = new <тип>[<размер 1>][][[] ...];
Пример: создание ступенчатого массива с тремя размерностями (графическая
иллюстрация приведена на рисунке 2.1)
int[][][] mas;
mas = new int[2][][];
// 1
mas[0]
=
new
int[2][];
//
2
mas[1]
=
new
int[3][];
//
3
mas[0][0] = new int[4]; // 4 mas[0][1] = new int[2]; // 5 mas[1][0] = new
int[3]; // 6
mas[1][1] = new int[2]; // 7 mas[1][2] = new int[4]; // 8
Рисунок 2.1 – Создание ступенчатого массива с тремя размерностями
Работа с массивами как с объектами
Переменная-массив фактически является ссылкой на область памяти, поэтому при
создании массивов используется оператора new.
Однако значение переменной-массиву может быть присвоено не только путём
создания нового объекта, но и путём присвоения ссылки на существующий объект,
например фрагмент кода
int[] mas1 = {1,2}; int[] mas2 = mas1;
создаёт две ссылки на одну и туже область данных. Это можно проверить следующим
кодом:
mas1[0] = 3;
MessageBox.Show(mas1[0].ToString()+" - "+mas2[0].ToString());
Вторая строка кода выведет сообщение «3 - 3», что подтверждает, что обе переменные
ссылаются на один и тот же массив.
Повторное создание массива для переменной mas1 с использованием
оператора new не изменит размерность существующего массива, а создаст новый массив,
что может подтвердить код
mas1 = new int[3] {4,5,6}; MessageBox.Show(mas1[0].ToString()+" -
"+mas2[0].ToString());
выполнение которого выведет сообщение «4 - 3». Это означает, что переменная mas1 ссылается на
новый массив, а переменная mas2 на старый. Если бы не было переменной mas2, то при повторном
создании массива не осталось бы ни одной ссылки на исходный массив, и он бы был уничтожен в
процессе «сборки мусора».
Так как каждый массив является объектом, то он обладает рядом единых для всех массивов
свойств и методов, некоторые из которых приведены в таблице 2.1.
выполнение которого выведет сообщение «4 - 3». Это означает, что
переменная mas1 ссылается на новый массив, а переменная mas2 на старый. Если бы не
было переменной mas2, то при повторном создании массива не осталось бы ни одной ссылки
на исходный массив, и он бы был уничтожен в процессе «сборки мусора».
Так как каждый массив является объектом, то он обладает рядом единых для всех
массивов свойств и методов, некоторые из которых приведены в таблице 2.1.
Таблица 2.1 – Некоторые свойства и методы массивов
Наименование
Описание
CopyTo
Копирует все элементы из текущего одномерного массива в
(Array array,
одномерный массив array, начиная их размещать начиная с
int index)
позиции index, например:
int[] mas = {1,2,3,4,5,6};
int[] mas2 = {7,8,9};
mas2.CopyTo(mas,2);
// mas = {1 2 7 8 9 6}
Если все элементы разместить невозможно, возникает ис-
ключение.
GetLength
Возвращает количество элементов в заданной размерности,
(int dimension)
например:
int[,] mas = {{1,2,3},{4,5,6}};
int i = mas.GetLength(0); // i = 2
int j = mas.GetLength(1); // j = 3
GetLowerBound
Возвращает значение индекса нижней границы в заданной
(int dimension)
размерности, например:
int[,] mas = {{1,2,3},{4,5,6}};
int i = mas.GetLowerBound(0); // i = 0
int j = mas.GetLowerBound(1); // j = 0
GetUpperBound
Возвращает значение индекса верхней границы в заданной
(int dimension)
размерности, например:
int[,] mas = {{1,2,3},{4,5,6}};
int i = mas.GetUpperBound(0); // i = 1
int j = mas.GetUpperBound(1); // j = 2
Length
Возвращает суммарное количество элементов массива во
всех размерностях, например:
int[,] mas = {{1,2,3},{4,5,6}};
int i = mas.Length; // i = 6
Rank
Возвращает ранг массива, например:
int[,] mas = {{1,2,3},{4,5,6}};
int i = mas.Rank; // i = 2
Оператор цикла foreach
Оператор цикла foreach может использоваться при обработке массивов в случае,
когда требуется обработать все элементы массива. Формальная структура оператора имеет
вид:
foreach (<тип> <идентификатор переменной цикла> in <идентификатор массива>)
<оператор>;
<тип> и дентификатор переменной цикла> описывают переменную, в
которую будет записываться текущее значение элемента массива <идентификатор
массива> на каждом этапе цикла. Поэтому <тип> должен совпадать с типом элемента
массива.
При работе цикла последовательно перебираются все элементы массива, независимо
от его размерности (т.е. оператор может обрабатывать и многомерные массивы).
Например, для нахождения суммы элементов двухмерного массива может быть
использован следующий код:
int[,] mas = ???;
int sum = 0;
foreach (int a in mas) sum += a;
При выполнении цикла переменная <идентификатор переменной
цикла> доступна только для чтения, поэтому изменить значение элемента массива с её
помощью нельзя.
Работу цикла можно прервать с использованием оператора break.
Строки
Строковый тип данных string (или String) представляет собой ссылку на объект,
хранящий последовательность символов – строку.
При работе со строковыми константами используются кавычки, например,
объявление и инициализация строки может быть записана как:
string s = "Пример";
Строка может быть задана с помощью массива символов, например:
char[] mas = {'П','р','и','м','е','р'}; string s = new string(mas); // s =
"Пример"
Несмотря на то, что строки являются ссылками на объекты, некоторые операции с
ними выполняются так, как если бы строки хранили сами значения:
оператор +. Позволяет объединять две строки, например, оператор
s = "При"+"мер"; присваивает строке s текст «Пример»;
операторы == и !=. Сравнивают строки по содержимому, а не по адресу. Например, во
фрагменте кода
string s1 = "Пример"; string s2 = "При"+"мер"; bool b = (s1 == s2);
значение переменной b будет true. Следует отметить, что другие операторы сравнения
при работе со строками недопустимы.
При выполнении операции сложения не требуется преобразование переменных к
строке, например:
int n = 5;
string s = "Значение переменной n равно "+n; //s = "Значение переменной n
равно 5"
Строки являются неизменяемыми объектами. Поэтому во всех операциях по
изменению строки на самом деле создаются новые объекты и разрушаются старые,
например, во фрагменте программы:
string s = "Пример";
s += " изменения строки";
создаётся новый объект-строка с текстом «Пример изменения строки», которая
присваивается переменной s. Старый объект-строка текстом «Пример») будет
уничтожен в процессе «сборки мусора».
Каждый элемент строки является символом. Доступ к элементам строки
осуществляется с использованием номера элемента умерация с нуля), заключённого в
квадратные скобки, например, для проверки, является ли первый элемент строки
символом «П» может быть использовано условие:
if (s[0] == 'П') ...;
Доступ к элементам строки не может быть использован для их модификации, т.е.
оператор s[1] = 'а'; является недопустимым.
Класс string предоставляет для работы со строками ряд методов и свойств,
которые могут вызваны с использованием как переменной данного класса (таблица 2.2),
так и с помощью самого класса (таблица 2.3).
Таблица 2.2 – Некоторые методы и свойства класса string, вызываемые через
переменную
Наименование
Описание
Length
Возвращает длину строки, например:
string s = "Пример";
int i = s.Length; // i = 6
CompareTo
Сравнивает текущую строку со строкой s (по алфавиту, а не по
(string s)
длине). Возвращает:
-1, если текущая строка расположена раньше (т.е. меньше), чем
строка s;
0, если строка s равна текущей;
1, если текущая строка расположена позднее (т.е. больше), чем
строка s.
string s1 = "абв";
string s2 = "гд";
string s3 = "абв";
int i = s1.CompareTo(s2); // i = -1
int j = s2.CompareTo(s3); // j = 1
int k = s1.CompareTo(s3); // k = 0
Contains
Возвращает true, если текущая строка содержит подстроку s, или
(string s)
подстрока s пустая (в противном случае возвращает false). Срав-
нение осуществляется с учётом регистра и без учёта региональных
настроек. Например:
string s = "Пример";
bool b1 = s.Contains("рим"); // b1 = true
bool b2 = s.Contains("Рим"); // b2 = false
StartsWith
Возвращает true, если текущая строка начинается с подстроки s
(string s)
противном случае возвращает false). Например:
string s = "Пример";
bool b1 = s.StartsWith("Прим"); // b1 = true
bool b2 = s.StartsWith("Прин"); // b2 = false
EndsWith
Возвращает true, если текущая строка заканчивается подстрокой s
(string s)
(в противном случае возвращает false). Например:
string s = "Пример";
bool b1 = s.EndsWith("мер"); // b1 = true
bool b2 = s.EndsWith("мера"); // b2 = false
IndexOf
Возвращает позицию первого вхождения подстроки s в текущей
(string s)
строке. Если подстрока не найдена, возвращается -1. Например:
string s = "Пример поиска подстроки";
int i1 = s.IndexOf("по"); // i1 = 7
int i2 = s.IndexOf("пол"); // i2 = -1
Продолжение таблицы 2.2
Наименование
Описание
LastIndexOf
Возвращает позицию последнего вхождения подстроки s в теку-
(string s)
щей строке. Если подстрока не найдена, возвращается -1. Напри-
мер:
string s = "Пример поиска подстроки";
int i1 = s.LastIndexOf("по"); // i1 = 14
int i2 = s.LastIndexOf("пол"); // i2 = -1
Insert
Возвращает строку, полученную путём вставки строки s в теку-
(int index,
щую строку начиная с позиции index. Например:
string s)
string s1 = "Слон ест мясо.";
string s2 = s1.Insert(5,"не ");
// s2 = "Слон не ест мясо."
Remove
Возвращает строку, полученную путём удаления из текущей
(int sIndex
строки всех (или count) символов начиная с позиции sIndex
[,int count])
(все параметры не должны выходить за пределы строки, в т.ч.
sIndex+count). Например:
string s1 = "Это не правда.";
string s2 = s1.Remove(4,3); // s2 = "Это правда."
Replace
Возвращает строку, полученную путём замены в текущей строке
(string oldS,
всех подстрок oldS на подстроки newS с учётом регистра и без
string newS)
учёта региональных настроек. Например:
string s1 = "трижды три будет 4";
string s2 = s1.Replace("три","два");
// s2 = "дважды два будет 4"
Substring
Возвращает строку, полученную путём извлечения из текущей
(int sIndex
строки всех (или count) символов начиная с позиции sIndex
[,int count])
(все параметры не должны выходить за пределы строки, в т.ч.
sIndex+count). Например:
string s1 = "Это не правда.";
string s2 = s1.Substring(7,6); // s2 = "правда"
Split
Возвращает массив строк, полученный путём разделения теку-
(char[] sep)
щей строке на подстроки, расположенные между разделителями
sep. Если два разделителя расположены в строке подряд (а так-
же, если разделителем является первый или последний символ),
то в массив добавляются пустые строки (от этого можно отка-
заться используя расширенные варианты метода). Например:
string s = " один, два,три четыре,";
char[] sep = { ' ', ',' };
string[] mas = s.Split(sep);
// mas = {"", "один", "", "два", "три", "четыре", ""}
Продолжение таблицы 2.2
Наименование
Описание
ToCharArray
Переводит текущую строку в массив символов. Если заданы пара-
([int
метры, то переводятся символы начиная с позиции sIndex в коли-
sIndex,
честве count. Например:
int count])
string s = "Пример";
char[] mas = s.ToCharArray(1,3);
// mas = ('р','и','м')
ToLower()
Возвращает строку, полученную путём приведения текущей
строки
ToUpper()
к нижнему (верхнему) регистру. Например:
string s1 = "ПриМер";
string s2 = s1.ToLower(); // s2 = "пример"
string s3 = s1.ToUpper(); // s3 = "ПРИМЕР"
Trim
Возвращает строку, полученную путём удаления из текущей
строки
([char[]
всех начальных (Trim, TrimStart) и конечных (Trim, TrimEnd)
tc])
пробелов (или символов, заданных в массиве tc). Например:
TrimStart
([char[]
string s1 = " Пример, ";
tc])
char[] tc = {' ',','};
TrimEnd
string s2 = s1.Trim(); // s2 = "Пример,"
([char[]
string s3 = s1.Trim(tc); // s3 = "Пример"
Таблица 2.3 – Некоторые методы и свойства класса string, вызываемые че-
рез сам класс
Наименование
Описание
Empty
Возвращает пустую строку (""). Т.е. строка есть, но состоит из 0
символов. Значение null означает, что строки вообще нет.
string s = String.Empty; // s = ""
Copy
Возвращает строку, полученную путём копирования строки s.
(string s)
Например:
string s1 = "Пример";
string s2 = String.Copy(s1); // s2 = "Пример"
Строка string s2 = s1; скопирует не значение, а ссылку!
IsNullOrEmpty
Возвращает true, если строка s имеет значение null или
()
String.Empty (в противном случае возвращает false). Напри-
мер:
string s = "Пример";
bool b = String.IsNullOrEmpty(s); // b = false
Продолжение таблицы 2.3
Наименование
Описание
Format
Возвращает строку, полученную путём форматирования ар-
(string s,
гументов a0[,a1 ...] с использованием строки форматирования
Object a0
s.
[,Object a1
Строка форматирования содержит произвольный текст с указанием
...])
мест, в которые должны быть вставлены отформатированные опре-
делённым образом аргументы. Места задаются фигурными скобка-
ми, содержащими номер аргумента и, при необходимости, способ
форматирования, отделённый от номера двоеточием.
Способ форматирования состоит из описателя формата и количе-
ства значащих цифр или десятичных знаков.
Некоторые описатели формата для чисел:
D или d – десятичный, используемый для целых чисел;
E или e – инженерный (экспоненциальный);
F или f – для вывода десятичных знаков;
N или n – числовой, с разделением разрядов;
X или x шестнадцатеричный;
P или p – процентный.
Например:
double d = 2.1;
string s1 = String.Format(
"Результат {0}*{0:F3} равен: {1:F1}",d,d*d);
// s1 = "Результат 2,1*2,100 равен: 4,4"
int i = 400;
string s2 = String.Format(
"Результат {0}*{0:D4} равен: {1:N1}",i,i*i);
// s2 = "Результат 400*0400 равен: 160 000,0"
При формировании строки возможно внедрение в неё Escapeпоследовательностей,
обеспечивающих дополнительное форматирование строк при выводе. Каждая Escape-
последовательность начинается с символа «\», после которого указывается тип
последовательности в виде некоторого символа. Например, если строка задана как
string s = "Это строка\nиз двух строк";
то при выводе данной строки она будет разделена на две: «Это строка» и «из двух строк».
Некоторые Escape-последовательности приведены в таблице 2.4.
Использование символа «\» для обозначения Escape-последовательности может
привести к неудобствам и ошибкам, например, если имя файла задано в строке
следующим образом
string s = "C:\new.txt";
то часть строки «\ будет интерпретирована как переход на новую строку, и файл найден
не будет.
Таблица 2.4 – Список Escape-последовательностей
Последовательность
Описание
\'
Вставка одиночной кавычки в текст строки
\"
Вставка двойной кавычки в текст строки
\\
Вставка обратной косой черты в текст строки
\n
Переход на новую строку
\r
Возврат каретки
\t
Вставка символа горизонтальной табуляции
Для отказа от использования в строке Escape-последовательностей, перед строкой
указывается символ «@», т.е. предыдущий пример может быть записан одним из двух
способов:
string s = "C:\\new.txt"; или string s = @"C:\new.txt";
Так как при применении символа «@» текст воспринимается без изменений, то в
нем могут быть введены символы табуляции, перехода на новую строку и т.п. Например,
если строка задана в виде
string s = @"Это первая строка Это вторая строка Это третья строка";
то при выводе такой строки она будет разделена на три.
Единственное преобразование, которое выполняется в строках, перед которыми
указана символ «@» это вставка в текст двойных кавычек. Для этого в месте вставки
двойной кавычки она указывается дважды:
string s = @"Он сказал ""Привет""";
Перечисления
Перечисление представляет собой набор имён, определяющих все возможные
значения которые могут быть назначены переменной.
Использование перечислений повышает читаемость кода программы и снижает
вероятность задания переменной недопустимого значения.
Формальное описание перечисления имеет вид:
enum <идентификатор перечисления>[: <тип>]
{
<идентификатор элемента 1>[=<значение 1>]
[,<идентификатор элемента 2>[=<значение 2>] ...]
}
По умолчанию тип элемента перечисления int. Однако он может быть изменён на другой
целочисленный тип путём указания блока . Задаваемые значения должны соответствовать типу
перечисления.
Пример: перечисление дней недели:
enum DayOfWeek
{
Monday,
Tuesday=5,
Wednesday,
Thursday,
Friday=7,
Saturday, Sunday
}
DayOfWeek d;
d = DayOfWeek.Monday;
Если элементам перечисления не назначены значения, то они нумеруются
последовательно с нуля. Если какому-нибудь элементу назначено значение, то следующий
элемент без значения, расположенный сразу за ним, получает значение на 1 больше.
Например в приведённом выше примере значения элементов перечисления будут
следующими:
Monday=0, Tuesday=5, Wednesday=6, Thursday=7, Friday=7, Saturday=8,
Sunday=9
Значения, назначаемые элементам перечисления, могут быть абсолютно любыми,
например:
enum WorldWar2Years
{
Start=1939,
End=1945,
Stalingrad=1942,
Kursk=1943
}
Обработка исключений
Исключительная ситуация (исключение) это возникновение в программе ошибочной
ситуации того или иного рода, например, деление на ноль, попытка преобразовать в число строку
и т.д.
В случае возникновения исключения программа прекращает выполнение текущего блока и
выдаёт сообщение об обнаруженной ошибке. Однако перехват и об-
39
работка возникающих ошибок позволяет улучшить контроль за выполнением программы.
В языке С# исключения представлены в виде классов. Все классы исключений являются
производными от встроенного в С# класса Exception, являющегося частью пространства
имён System.
Обработка исключительных ситуаций в С# организуется с помощью четырёх ключевых
слов: try, catch, throw и finally. Они образуют взаимосвязанную подсистему, в которой
применение одного из ключевых слов подразумевает применение другого.
Класс Exception и стандартные исключения
Класс Exception, являясь родителем всех классов исключений, предоставляет им
единые свойства и методы, некоторыми из которых являются:
Message текст, описывающий исключение;
StackTrace позволяет получить стек вызовов для определения места возникновения
исключения;
GetType() возвращает тип исключения. В сочетании с методом ToString() имеется
возможность получить строковое представление типа исключения.
Некоторые стандартные исключения приведены в таблице 2.5.
Таблица 2.5 – Некоторые стандартные исключения
Наименование
Причина возникновения исключения
AccessViolationException
попытка чтения или записи в защищённую об-
ласть памяти
ArithmeticException
ошибки в арифметических действий, а также
операциях приведения к типу и преобразования
DivideByZeroException
попытка деления на ноль. Для вещественных чи-
сел не возникает, т.к. там используются значения
± бесконечность или нечисловое значение
OverflowException
переполнение при выполнении арифметических
операций, операций приведения типов и преоб-
разования
FormatException
ошибка при преобразовании из одного типа дан-
ных в другой, например, при преобразовании из
строки в число методами класса Convert
IndexOutOfRangeException
попытка обращения к элементу массива с индек-
сом, который находится вне границ массива
InvalidCastException
недопустимое приведение или явное преобразо-
вание типов
IOException
ошибки ввода-вывода
DirectoryNotFoundException
невозможно найти часть файла или каталога
DriveNotFoundException
попытка доступа к недоступному диску или
данным совместного использования
EndOfStreamException
попытка выполнить чтение за пределами потока
FileNotFoundException
попытка доступа к файлу, не существующему на
диске
PathTooLongException
путь или имя файла превышает максимальную
длину, определённую системой
NullReferenceException
попытка использовать пустую ссылку, т.е.
ссылку, ко торая не указывает ни на один из
объектов
Иерархия некоторых классов исключений имеет вид:
Exception
SystemException
AccessViolationException
ArithmeticException
DivideByZeroException
OverflowException
FormatException
IndexOutOfRangeException
InvalidCastException
IOException
DirectoryNotFoundException
DriveNotFoundException
EndOfStreamException
FileNotFoundException
PathTooLongException
NullReferenceException
Знание иерархии классов важно для правильной обработки возникающих
исключений.
Блок try...catch
Основу обработки исключительных ситуаций в С# составляет пара ключевых
слов try и catch, формальное использование которых имеет вид:
try
{
<блок кода, проверяемый на ошибки>
}
catch (<тип исключения 1> [<переменная исключения 1>])
{
<обработка исключения типа 1>
}
[catch (<тип исключения 2> [<переменная исключения 2>])
{
<обработка исключения типа 2> } ...]
Принцип работы блока try...catch следующий. При возникновении ошибки в
блоке <блок кода, проверяемый на ошибки> дальнейшее выполнение данного блока
прекращается и управление передаются тому блоку catch (т.е. вы-
полняется блок <обработка исключения типа N>), у которого <тип исключения
N> совпадает с типом возникшей ошибки. Если при выполнении блока <обработка
исключения типа N> требуется доступ к параметрам исключения, то в блоке catch может
быть описана переменная <переменная исключения N>.
Если в блоке <блок кода, проверяемый на ошибки> не возникло оши-
бок, то все блоки catch пропускаются.
При использовании нескольких блоков catch все типы исключений, которые они
обрабатывают, должны быть разными. При этом, если между двумя типами исключений
есть связь «родитель–потомок», то сначала должна быть описана обработка «исключения–
потомка».
Пример: найти результат целочисленного деления числа 1000 на число a, которое
пользователь вводит в компоненте A_TB класса TextBox. Результат вывести в
компонент R_TB класса TextBox.
try
{
int a = Convert.ToInt32(A_TB.Text); R_TB.Text = String.Format(
"Результат выражения 1000/{0} равен {1}", a, 1000/a);
}
catch (FormatException)
{
R_TB.Text = "Ошибка: число А должно быть целым";
}
catch (DivideByZeroException)
{
R_TB.Text = "Ошибка: деление на 0";
}
Блоки try...catch могут быть вложенными. При этом, если исключение
возникает во внутреннем блоке, и там не обрабатывается, то оно передаётся во внешний
блок, где может быть обработано.
Пример: найти результат целочисленного деления числа 1000 на число a, которое
пользователь вводит в компоненте A_TB класса TextBox. Результат вывести в
компонент R_TB класса TextBox. Если число введено неправильно, то считать его
значение равным 1.
try
{
int a; try
{
a = Convert.ToInt32(A_TB.Text);
}
catch (FormatException)
{
a = 1;
}
R_TB.Text = String.Format(
"Результат выражения 1000/{0} равен {1}", a, 1000/a);
}
catch (DivideByZeroException)
{
R_TB.Text = "Ошибка: деление на 0";
}
В блоке catch может не указываться тип исключения. В этом случае такой блок
располагается самым последним и обрабатывает все типы исключений, не обработанные
до него.